/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the  "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
/*
 * $Id$
 */
package wx.xml.xalan.xalan.templates;

import java.util.StringTokenizer;
import java.util.Vector;

import javax.xml.transform.TransformerException;

import wx.xml.xalan.xalan.processor.StylesheetHandler;
import wx.xml.xalan.xalan.res.XSLMessages;
import wx.xml.xalan.xalan.res.XSLTErrorResources;
import wx.xml.xalan.xml.utils.FastStringBuffer;
import wx.xml.xalan.xml.utils.StringBufferPool;
import wx.xml.xalan.xpath.XPath;
import wx.xml.xalan.xpath.XPathContext;

/**
 * Class to hold an Attribute Value Template.
 *
 * @xsl.usage advanced
 */
public class AVT implements java.io.Serializable, XSLTVisitable {
    static final long serialVersionUID = 5167607155517042691L;

    /**
     * We are not going to use the object pool if USE_OBJECT_POOL == false.
     */
    private final static boolean USE_OBJECT_POOL = false;

    /**
     * INIT_BUFFER_CHUNK_BITS is used to set initial size of
     * of the char m_array in FastStringBuffer if USE_OBJECT_POOL == false.
     * size = 2^ INIT_BUFFER_CHUNK_BITS, INIT_BUFFER_CHUNK_BITS = 7
     * corresponds size = 256.
     */
    private final static int INIT_BUFFER_CHUNK_BITS = 8;

    /**
     * If the AVT is not complex, just hold the simple string.
     *
     * @serial
     */
    private String m_simpleString = null;

    /**
     * If the AVT is complex, hold a Vector of AVTParts.
     *
     * @serial
     */
    private Vector m_parts = null;


    /**
     * The name of the attribute.
     *
     * @serial
     */
    private String m_rawName;
    /**
     * The name of the attribute.
     *
     * @serial
     */
    private String m_name;
    /**
     * The namespace URI of the owning attribute.
     *
     * @serial
     */
    private String m_uri;

    /**
     * Construct an AVT by parsing the string, and either
     * constructing a vector of AVTParts, or simply hold
     * on to the string if the AVT is simple.
     *
     * @param handler       non-null reference to StylesheetHandler that is constructing.
     * @param uri           non-null reference to URI, "" if null namespace.
     * @param name          non-null reference to name string.
     * @param rawName       prefixed name.
     * @param stringedValue non-null raw string value.
     * @throws javax.xml.transform.TransformerException
     */
    public AVT(StylesheetHandler handler, String uri, String name,
               String rawName, String stringedValue,
               ElemTemplateElement owner)
        throws javax.xml.transform.TransformerException {

        m_uri = uri;
        m_name = name;
        m_rawName = rawName;

        StringTokenizer tokenizer = new StringTokenizer(stringedValue, "{}\"\'",
            true);
        int nTokens = tokenizer.countTokens();

        if (nTokens < 2) {
            m_simpleString = stringedValue;  // then do the simple thing
        } else {
            FastStringBuffer buffer     = null;
            FastStringBuffer exprBuffer = null;
            if (USE_OBJECT_POOL) {
                buffer = StringBufferPool.get();
                exprBuffer = StringBufferPool.get();
            } else {
                buffer = new FastStringBuffer(6);
                exprBuffer = new FastStringBuffer(6);
            }
            try {
                m_parts = new Vector(nTokens + 1);

                String t         = null;  // base token
                String lookahead = null;  // next token
                String error     = null;  // if non-null, break from loop

                while (tokenizer.hasMoreTokens()) {
                    if (lookahead != null) {
                        t = lookahead;
                        lookahead = null;
                    } else
                        t = tokenizer.nextToken();

                    if (t.length() == 1) {
                        switch (t.charAt(0)) {
                            case ('\"'):
                            case ('\''): {

                                // just keep on going, since we're not in an attribute template
                                buffer.append(t);

                                break;
                            }
                            case ('{'): {

                                try {
                                    // Attribute Value Template start
                                    lookahead = tokenizer.nextToken();

                                    if (lookahead.equals("{")) {

                                        // Double curlys mean escape to show curly
                                        buffer.append(lookahead);

                                        lookahead = null;

                                        break;  // from switch
                                    }

                /*
                else if(lookahead.equals("\"") || lookahead.equals("\'"))
                {
                // Error. Expressions can not begin with quotes.
                error = "Expressions can not begin with quotes.";
                break; // from switch
                }
                */
                                    else {
                                        if (buffer.length() > 0) {
                                            m_parts.addElement(new AVTPartSimple(buffer.toString()));
                                            buffer.setLength(0);
                                        }

                                        exprBuffer.setLength(0);

                                        while (null != lookahead) {
                                            if (lookahead.length() == 1) {
                                                switch (lookahead.charAt(0)) {
                                                    case '\'':
                                                    case '\"': {

                                                        // String start
                                                        exprBuffer.append(lookahead);

                                                        String quote = lookahead;

                                                        // Consume stuff 'till next quote
                                                        lookahead = tokenizer.nextToken();

                                                        while (!lookahead.equals(quote)) {
                                                            exprBuffer.append(lookahead);

                                                            lookahead = tokenizer.nextToken();
                                                        }

                                                        exprBuffer.append(lookahead);

                                                        lookahead = tokenizer.nextToken();

                                                        break;
                                                    }
                                                    case '{': {

                                                        // What's another curly doing here?
                                                        error = XSLMessages.createMessage(
                                                            XSLTErrorResources.ER_NO_CURLYBRACE, null);  //"Error: Can not have \"{\" within expression.";

                                                        lookahead = null;  // breaks out of inner while loop

                                                        break;
                                                    }
                                                    case '}': {

                                                        // Proper close of attribute template.
                                                        // Evaluate the expression.
                                                        buffer.setLength(0);

                                                        XPath xpath =
                                                            handler.createXPath(exprBuffer.toString(), owner);

                                                        m_parts.addElement(new AVTPartXPath(xpath));

                                                        lookahead = null;  // breaks out of inner while loop

                                                        break;
                                                    }
                                                    default: {

                                                        // part of the template stuff, just add it.
                                                        exprBuffer.append(lookahead);

                                                        lookahead = tokenizer.nextToken();
                                                    }
                                                }  // end inner switch
                                            }  // end if lookahead length == 1
                                            else {

                                                // part of the template stuff, just add it.
                                                exprBuffer.append(lookahead);

                                                lookahead = tokenizer.nextToken();
                                            }
                                        }  // end while(!lookahead.equals("}"))

                                        if (error != null) {
                                            break;  // from inner while loop
                                        }
                                    }

                                    break;
                                } catch (java.util.NoSuchElementException ex) {
                                    error = XSLMessages.createMessage(XSLTErrorResources.ER_ILLEGAL_ATTRIBUTE_VALUE, new Object[]{name, stringedValue});
                                    break;
                                }
                            }
                            case ('}'): {
                                lookahead = tokenizer.nextToken();

                                if (lookahead.equals("}")) {

                                    // Double curlys mean escape to show curly
                                    buffer.append(lookahead);

                                    lookahead = null;  // swallow
                                } else {

                                    // Illegal, I think...
                                    try {
                                        handler.warn(XSLTErrorResources.WG_FOUND_CURLYBRACE, null);  //"Found \"}\" but no attribute template open!");
                                    } catch (org.xml.sax.SAXException se) {
                                        throw new TransformerException(se);
                                    }

                                    buffer.append("}");

                                    // leave the lookahead to be processed by the next round.
                                }

                                break;
                            }
                            default: {

                                // Anything else just add to string.
                                buffer.append(t);
                            }
                        }  // end switch t
                    }  // end if length == 1
                    else {

                        // Anything else just add to string.
                        buffer.append(t);
                    }

                    if (null != error) {
                        try {
                            handler.warn(XSLTErrorResources.WG_ATTR_TEMPLATE,
                                new Object[]{error});  //"Attr Template, "+error);
                        } catch (org.xml.sax.SAXException se) {
                            throw new TransformerException(se);
                        }

                        break;
                    }
                }  // end while(tokenizer.hasMoreTokens())

                if (buffer.length() > 0) {
                    m_parts.addElement(new AVTPartSimple(buffer.toString()));
                    buffer.setLength(0);
                }
            } finally {
                if (USE_OBJECT_POOL) {
                    StringBufferPool.free(buffer);
                    StringBufferPool.free(exprBuffer);
                } else {
                    buffer = null;
                    exprBuffer = null;
                }
                ;
            }
        }  // end else nTokens > 1

        if (null == m_parts && (null == m_simpleString)) {

            // Error?
            m_simpleString = "";
        }
    }

    /**
     * Get the raw name of the attribute, with the prefix unprocessed.
     *
     * @return non-null reference to prefixed name.
     */
    public String getRawName() {
        return m_rawName;
    }

    /**
     * Get the raw name of the attribute, with the prefix unprocessed.
     *
     * @param rawName non-null reference to prefixed name.
     */
    public void setRawName(String rawName) {
        m_rawName = rawName;
    }

    /**
     * Get the local name of the attribute.
     *
     * @return non-null reference to name string.
     */
    public String getName() {
        return m_name;
    }

    /**
     * Set the local name of the attribute.
     *
     * @param name non-null reference to name string.
     */
    public void setName(String name) {
        m_name = name;
    }

    /**
     * Get the namespace URI of the attribute.
     *
     * @return non-null reference to URI, "" if null namespace.
     */
    public String getURI() {
        return m_uri;
    }

    /**
     * Get the namespace URI of the attribute.
     *
     * @param uri non-null reference to URI, "" if null namespace.
     */
    public void setURI(String uri) {
        m_uri = uri;
    }

    /**
     * Get the AVT as the original string.
     *
     * @return The AVT as the original string
     */
    public String getSimpleString() {

        if (null != m_simpleString) {
            return m_simpleString;
        } else if (null != m_parts) {
            final FastStringBuffer buf = getBuffer();
            String                 out = null;

            int n = m_parts.size();
            try {
                for (int i = 0; i < n; i++) {
                    AVTPart part = (AVTPart) m_parts.elementAt(i);
                    buf.append(part.getSimpleString());
                }
                out = buf.toString();
            } finally {
                if (USE_OBJECT_POOL) {
                    StringBufferPool.free(buf);
                } else {
                    buf.setLength(0);
                }
                ;
            }
            return out;
        } else {
            return "";
        }
    }

    /**
     * Evaluate the AVT and return a String.
     *
     * @param xctxt   Te XPathContext to use to evaluate this.
     * @param context The current source tree context.
     * @param nsNode  The current namespace context (stylesheet tree context).
     * @return The AVT evaluated as a string
     * @throws javax.xml.transform.TransformerException
     */
    public String evaluate(
        XPathContext xctxt, int context, wx.xml.xalan.xml.utils.PrefixResolver nsNode)
        throws javax.xml.transform.TransformerException {
        if (null != m_simpleString) {
            return m_simpleString;
        } else if (null != m_parts) {
            final FastStringBuffer buf = getBuffer();
            String                 out = null;
            int                    n   = m_parts.size();
            try {
                for (int i = 0; i < n; i++) {
                    AVTPart part = (AVTPart) m_parts.elementAt(i);
                    part.evaluate(xctxt, buf, context, nsNode);
                }
                out = buf.toString();
            } finally {
                if (USE_OBJECT_POOL) {
                    StringBufferPool.free(buf);
                } else {
                    buf.setLength(0);
                }
            }
            return out;
        } else {
            return "";
        }
    }

    /**
     * Test whether the AVT is insensitive to the context in which
     * it is being evaluated. This is intended to facilitate
     * compilation of templates, by allowing simple AVTs to be
     * converted back into strings.
     * <p>
     * Currently the only case we recognize is simple strings.
     * ADDED 9/5/2000 to support compilation experiment
     *
     * @return True if the m_simpleString member of this AVT is not null
     */
    public boolean isContextInsensitive() {
        return null != m_simpleString;
    }

    /**
     * Tell if this expression or it's subexpressions can traverse outside
     * the current subtree.
     *
     * @return true if traversal outside the context node's subtree can occur.
     */
    public boolean canTraverseOutsideSubtree() {

        if (null != m_parts) {
            int n = m_parts.size();

            for (int i = 0; i < n; i++) {
                AVTPart part = (AVTPart) m_parts.elementAt(i);

                if (part.canTraverseOutsideSubtree())
                    return true;
            }
        }

        return false;
    }

    /**
     * This function is used to fixup variables from QNames to stack frame
     * indexes at stylesheet build time.
     *
     * @param vars List of QNames that correspond to variables.  This list
     *             should be searched backwards for the first qualified name that
     *             corresponds to the variable reference qname.  The position of the
     *             QName in the vector from the start of the vector will be its position
     *             in the stack frame (but variables above the globalsTop value will need
     *             to be offset to the current stack frame).
     */
    public void fixupVariables(java.util.Vector vars, int globalsSize) {
        if (null != m_parts) {
            int n = m_parts.size();

            for (int i = 0; i < n; i++) {
                AVTPart part = (AVTPart) m_parts.elementAt(i);

                part.fixupVariables(vars, globalsSize);
            }
        }
    }

    /**
     * @see XSLTVisitable#callVisitors(XSLTVisitor)
     */
    public void callVisitors(XSLTVisitor visitor) {
        if (visitor.visitAVT(this) && (null != m_parts)) {
            int n = m_parts.size();

            for (int i = 0; i < n; i++) {
                AVTPart part = (AVTPart) m_parts.elementAt(i);

                part.callVisitors(visitor);
            }
        }
    }


    /**
     * Returns true if this AVT is simple
     */
    public boolean isSimple() {
        return m_simpleString != null;
    }

    private final FastStringBuffer getBuffer() {
        if (USE_OBJECT_POOL) {
            return StringBufferPool.get();
        } else {
            return new FastStringBuffer(INIT_BUFFER_CHUNK_BITS);
        }
    }
}
